
using System;
using Microsoft.Win32;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.AccessControl;
using System.Security;

namespace RegHelper
{
    public enum RootRegistryPath
    {
        Unknown,
        CurrentUser,
        CurrentConfig,
        Users,
        LocalMachine,
        ClassesRoot,
        PerformanceData
    };
    class Util
    {
        static public void PrintException(Exception e)
        {
            string show = e.GetType().ToString() + "\r\n" + e.TargetSite + " :: " + e.Message;
            Debug.WriteLine(show);
            Console.WriteLine(show);
        }
    }


    public class RegPath : RegPathSpec, IDisposable
    {
        bool _writable;
        RegistryKey _current = null;
        RootRegistryPath _root = RootRegistryPath.Unknown;
        string _path = null;

        public RegistryKey Handler { get { return this._current; } }

        private RegPath(RootRegistryPath root, string path, bool IsReadOnly)
        {
            Debug.Assert(path != null);
            Debug.Assert(root != RootRegistryPath.Unknown);

            this._writable = !IsReadOnly;
            this._path = path;
            this._root = root;
        }

        // readonly path
        RegistryKey _read = null; // available so we don't leak resources, and easy to dispose of.

        internal RegistryKey ReadPath
        {
            get
            {
                if (_read == null)
                {
                    _read = GetRoot().OpenSubKey(this._path); // null result means it doesn't exist yet.
                }
                return _read;
            }
        }

        public bool Exist()
        {
            return this.ReadPath != null;
        }

        /// <summary>
        /// may fail, check with Valid() afterwards.
        /// </summary>
        public void Open()
        {
            try
            {
                var opened = GetRoot().OpenSubKey(this._path, this._writable);
                this._current = opened;
            }
            // just placeholders.
            catch (ArgumentException ae)
            {
                Util.PrintException(ae);
            }
            catch (ObjectDisposedException ode)
            {
                Util.PrintException(ode);
            }
            catch (SecurityException se)
            {
                Util.PrintException(se);
            }
            catch (Exception e)
            {
                Util.PrintException(e);
            }
        }

        /// <summary>
        /// opens or creates a new registry path (if doesn't exist), and returns a RegPath object.
        /// </summary>
        /// <param name="root">enum RootRegistryPath: choose one base "folder" to work from</param>
        /// <param name="path">path after the base folder. use forward slashes like so: "Folder1\\Folder2\\myFolder"</param>
        /// <param name="isReadonly">should the path be opened just for reading. if the path doesn't exist, the registry will be modified anyway to create it.</param>
        /// <returns></returns>
        public static RegPath NewPath(RootRegistryPath root, string path, bool isReadonly)
        {
            var regpath = MinimalNewPath(root, path, isReadonly);
            if (regpath.Exist())
            {
                regpath.Open();
            }
            else
            {
                regpath.Create();
            }
            return regpath;
        }

        /// <summary>
        /// note: unsafe, but you may need it.
        /// use if Exist(), Open(), and Create(), and then verify that it's Valid() for further accesses.
        /// </summary>
        public static RegPath MinimalNewPath(RootRegistryPath root, string path, bool isReadonly)
        {
            var regpath = new RegPath(root, path, isReadonly);
            return regpath;
        }

        RegistryKey GetRoot()
        {
            RegistryKey key = null;

            switch (this._root)
            {
                case RootRegistryPath.ClassesRoot:
                    key = Registry.ClassesRoot;
                    break;
                case RootRegistryPath.CurrentConfig:
                    key = Registry.CurrentConfig;
                    break;
                case RootRegistryPath.CurrentUser:
                    key = Registry.CurrentUser;
                    break;
                case RootRegistryPath.LocalMachine:
                    key = Registry.LocalMachine;
                    break;
                case RootRegistryPath.PerformanceData:
                    key = Registry.PerformanceData;
                    break;
                case RootRegistryPath.Users:
                    key = Registry.Users;
                    break;


            }
            return key;
        }

        /// <summary>
        /// Create the path. Check first that it doesn't exist with path.Exist() .
        /// This operation may fail. Check with Valid() the result.
        /// Note: For regular usage, recommended to use the static NewPath.
        /// </summary>
        public void Create()
        {
            Debug.Assert(!this.Exist());
            Debug.Assert(this._current == null, "check that the path isn't already created with Exist");
            try
            {
                var created = GetRoot().CreateSubKey(this._path, RegistryKeyPermissionCheck.ReadWriteSubTree);
                this._current = created;
            }
            catch (Exception e)
            {
                Util.PrintException(e);
            }
        }

        public bool Valid { get {return this._current != null; }}

        /// <summary>
        /// returns a list of the key names (like filenames) from the current registry folder.
        /// </summary>
        /// <returns></returns>
        public string[] ls()
        {
            // the precondition is that the path exists.
            Debug.Assert(this.Exist());

            var result = new string[] { };

            if (this.ReadPath != null)
            {
                try
                {
                    result = this.ReadPath.GetValueNames();
                }
                // TODO: specific catch phrases, if needed later on.
                catch (Exception e)
                { Util.PrintException(e); }
            }
            return result;
        }

        /// <summary>
        /// gets the parent folder.
        /// if no parent, will return the base/root folder.
        /// </summary>
        /// <returns></returns>
        public RegPath Parent()
        {
            Debug.Assert(this._current != null);
            var rootdir = RegPath.NewPath(this._root, "", false); // default result.

            var fullpath = this._current.Name;
     

            string mod1 = "";
            if (fullpath.Contains("\\"))
            {
                // remove the base folder. (if possible)
                mod1 = fullpath.Substring(fullpath.IndexOf('\\')).TrimStart('\\');
            }
            if (mod1.Contains("\\"))
            {
                // remove the last (current) folder. (if possible)
                mod1 = mod1.Substring(0, mod1.LastIndexOf('\\')).TrimEnd('\\');
            }
            else  // only one folder remained, so return root.
                return rootdir;

            string parent_path = mod1;

            // create new RegPath object from parent path.
            var parent = RegPath.NewPath(this._root, parent_path, false);
            return parent;
        }


        #region IDisposable Members
        public void Dispose() // called from user code
        {
            dispose(true);
            GC.SuppressFinalize(this);
        }
        protected void dispose(bool fromuser)
        {
            if (fromuser)
            {
                // cleanup resources from other objects (not 'this')
                // if from user, we assume they may exist by now.
                // if from gc, we cannot assume.
            }

            // do this part anyway
            if (this._read != null)
                this._read.Close();
            if (this._current != null)
                this._current.Close();

        }

        ~RegPath() // for finalizer, if we just set the object to null and it's GCed.
        {
            dispose(false);
        }

        #endregion
    }

}